package othello.server.memcacheutil;

import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.slim3.memcache.Memcache;

import othello.shared.model.StateAtLounge;
import othello.util.SynchronizedRunner;

/**
 * Entry を管理するクラス
 * 
 * @author onjiro.mohyahya
 */
public final class LoungeStateCacher {

	/**
	 * Entry の保存用キー名の一覧のキー
	 */
	public static final String KEY_ENTRY_KEYS = "entryKeys";

	/**
	 * ロガー
	 */
	private static final Logger LOGGER = Logger
			.getLogger(LoungeStateCacher.class.getName());

	/**
	 * Syncronized ブロック実装
	 */
	private static final SynchronizedRunner SYNC_PART = new SynchronizedRunner(
			"entryLock" + LoungeStateCacher.class.getName());

	/**
	 * <p>
	 * セッションIDと状態を紐付けて登録します。
	 * </p>
	 * ただし、該当のセッションが既に使用されている場合は登録を行いません。
	 * 
	 * @param sessionId
	 *            セッションID
	 * @param state
	 *            セッションに紐付けられる状態
	 * @throws SynchronizedRunner.LockFailed
	 *             ロックを獲得出来なかった場合
	 */
	public static void entry(final String sessionId, final StateAtLounge state)
			throws LockFailed {
		SYNC_PART.run(new Runnable() {
			@Override
			public void run() {
				StateAtLoungeManager.put(sessionId, state);
			};
		});
	}

	/**
	 * 登録 StateAtLounge 取得メソッド
	 * 
	 * @param sessionId
	 * @return
	 */
	public static StateAtLounge get(String sessionId) {
		return StateAtLoungeManager.get(sessionId);
	}

	/**
	 * マッチング処理を実行し、新規のゲームボードIDを取得します。
	 * 
	 * @param sessionId
	 *            マッチング処理を依頼するセッションのID
	 * @return 成功：ゲームボードID、失敗：null
	 * @throws LockFailed
	 *             ロック処理に失敗した場合
	 */
	public static StateAtLounge matchAnyone(final String sessionId)
			throws LockFailed {

		return SYNC_PART.run(new SynchronizedRunner.Runnable<StateAtLounge>() {
			@Override
			public StateAtLounge run() {
				// 待機状態の相手を検索
				List<String> sessions = StateAtLoungeManager.getAllSessionIds();
				for (String id : sessions) {
					StateAtLounge partnerState = StateAtLoungeManager.get(id);
					if (partnerState != null
							&& partnerState.getGameBoardId() == null) {
						// 待機状態の相手があれば、ゲームボードIDを作成して相手の状態を更新する。
						String newBoardId = GameBoardFactory
								.createGameBoardId();
						partnerState.setGameBoardId(newBoardId);
						partnerState.setPartnerId(sessionId);
						StateAtLoungeManager.put(id, partnerState);
						return partnerState;
					}
				}
				return null;
			}
		});
	}

	/**
	 * 指定したセッションID状態の登録を削除します。
	 * 
	 * @param sessionId
	 *            状態を削除するセッションID
	 * @throws SynchronizedRunner.LockFailed
	 *             ロックを獲得出来なかった場合
	 */
	public static void remove(final String sessionId) {
		SYNC_PART.run(new Runnable() {
			@Override
			public void run() {
				StateAtLoungeManager.remove(sessionId);
			}
		});
	}

	/**
	 * StateAtLoungeのキャッシュ管理動作を引き受けるクラス
	 * 
	 * @author onjiro.mohyahya
	 */
	private static final class StateAtLoungeManager {

		/**
		 * セッションIDをキーとして指定された状態をキャッシュします。
		 * 
		 * @param sessionId
		 *            セッションID
		 * @param state
		 *            セッションIDに紐づく状態
		 */
		public static void put(String sessionId, StateAtLounge state) {
			// セッション一覧の更新と、状態の登録
			List<String> sessions = getAllSessionIds();
			if (!sessions.contains(sessionId)) {
				sessions.add(sessionId);
				Memcache.put(KEY_ENTRY_KEYS, sessions);
			}
			Memcache.put(sessionId, state);

		}

		/**
		 * 現在のキャッシュから指定されたセッションIDの状態を取得します。
		 * 
		 * @param sessionId
		 *            取得対象のセッションID
		 * @return セッションIDに紐づく状態
		 */
		public static StateAtLounge get(String sessionId) {
			return (getAllSessionIds().contains(sessionId)) ? (StateAtLounge) Memcache
					.get(sessionId)
					: null;
		}

		/**
		 * 現在のキャッシュから指定されたセッションIDの状態を削除します。
		 * 
		 * @param sessionId
		 *            削除対象のセッションID
		 */
		public static void remove(String sessionId) {
			// セッション一覧の更新と、状態の登録
			List<String> sessions = getAllSessionIds();
			if (sessions.contains(sessionId)) {
				sessions.remove(sessionId);
				Memcache.put(KEY_ENTRY_KEYS, sessions);
				boolean deleted = Memcache.delete(sessionId);
				Object[] parameters = { Memcache.class.getName(), sessionId,
						Boolean.valueOf(deleted) };
				LOGGER.log(Level.INFO, "{0} delete({1}) # => {2}", parameters);
			}
		}

		/**
		 * 現在キャッシュされているセッションIDの一覧を取得します。
		 * 
		 * @return セッションIDのリスト
		 */
		@SuppressWarnings("unchecked")
		public static List<String> getAllSessionIds() {
			// 接続中のセッション一覧の取得。存在しなかった場合、新規登録
			List<String> sessions = (List<String>) Memcache.get(KEY_ENTRY_KEYS);
			return (sessions != null) ? sessions : new LinkedList<String>();
		}

	}

}
